<?php

namespace app\logic;

use app\Api;

/**
 * 用户支付宝类
 * Class AliPay
 * @package app\index\logic
 */
class AliPay extends Api
{
    private static $obj = null;

    public function _initialize()
    {
        parent::_initialize();
    }

    /**
     * 类入口
     * @return AliPay|null
     */
    public static function getInstance()
    {
        if (is_null(self::$obj))
            self::$obj = new self();
        return self::$obj;
    }

    /**
     * 支付宝APP支付
     * @param $str_out_trade_no
     * @param $float_order_amount
     * @return string
     */
    public function aliAppPay($str_out_trade_no, $float_order_amount,$module)
    {
        $str_notify_url = config('queue.ali_app_pay_back_url'); // 支付宝回调地址

        //支付宝分配给开发者的应用ID
        $arr_data['app_id'] = config('alipay.'.$module)['app_id'];
        //接口名称
        $arr_data['method'] = 'alipay.trade.app.pay';
        //请求使用的编码格式，如utf-8,gbk,gb2312等
        $arr_data['charset'] = 'utf-8';
        //商户生成签名字符串所使用的签名算法类型
        $arr_data['sign_type'] = 'RSA2';
        //发送请求的时间，格式"yyyy-MM-dd HH:mm:ss"
        $arr_data['timestamp'] = $this->_now;
        //调用的接口版本，固定为：1.0
        $arr_data['version'] = '1.0';
        //支付宝服务器主动通知商户服务器里指定的页面http/https路径
        $arr_data['notify_url'] = $str_notify_url;
        //请求参数的集合，最大长度不限，除公共参数外所有请求参数都必须放在这个参数中传递，具体参照各产品快速接入文档

        //商户订单号,64个字符以内、可包含字母、数字、下划线；需保证在商户端不重复
        $arr_data['biz_content']['out_trade_no'] = $str_out_trade_no;
        //订单总金额，单位为元，精确到小数点后两位，取值范围[0.01,100000000] 如果同时传入了【打折金额】，【不可打折金额】，【订单总金额】三者，则必须满足如下条件：【订单总金额】=【打折金额】+【不可打折金额】
        $arr_data['biz_content']['total_amount'] = $float_order_amount;

        //主题
        $arr_data['biz_content']['subject'] = '购买商品';
        //销售产品码，商家和支付宝签约的产品码，为固定值QUICK_MSECURITY_PAY
        $arr_data['biz_content']['product_code'] = 'QUICK_MSECURITY_PAY';

        $arr_data['biz_content']['passback_params'] = urlencode($module);

        // 公共回传参数，如果请求时传递了该参数，则返回给商户时会在异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝
        //服务器端异步响应地址
        $arr_data['biz_content'] = json_encode($arr_data['biz_content']);

        $arr_pay_info = $this->getSignContent($arr_data);
        $str_pay_info = $this->getSignContent($arr_data, 1);
        //签名
        $str_sign = $this->alipaySign($module,$arr_pay_info);

        //生成最终签名信息
        $str_order_info = $str_pay_info . '&sign=' . urlencode($str_sign);

        // base64用于防止数据被转义
        return ($str_order_info);
    }

    /**
     * 支付宝退款
     * @param $str_out_trade_no
     * @param $float_refund_amount
     * @param $str_out_request_no
     * @return string
     */
    public function alipayRefund($str_out_trade_no, $float_refund_amount, $str_out_request_no,$module)
    {
        //支付宝分配给开发者的应用ID
        $arr_data['app_id'] = config('alipay.'.$module)['app_id'];
        //接口名称
        $arr_data['method'] = 'alipay.trade.refund';
        //请求使用的编码格式，如utf-8,gbk,gb2312等
        $arr_data['charset'] = 'utf-8';
        //商户生成签名字符串所使用的签名算法类型
        $arr_data['sign_type'] = 'RSA2';
        //发送请求的时间，格式"yyyy-MM-dd HH:mm:ss"
        $arr_data['timestamp'] = $this->_now;
        //调用的接口版本，固定为：1.0
        $arr_data['version'] = '1.0';
        //商家订单号
        $arr_data['biz_content']['out_trade_no'] = $str_out_trade_no;
        //退款金额
        $arr_data['biz_content']['refund_amount'] = $float_refund_amount;
        //退款原因
        //$mygoods['biz_content']['refund_reason'] = $refund_reason;
        //退款唯一标识
        $arr_data['biz_content']['out_request_no'] = $str_out_request_no;
        //商户的操作员编号
        //$mygoods['biz_content']['operator_id'] = $operator_id;
        //商户的门店编号
        //$mygoods['biz_content']['store_id'] = $store_id;
        //商户的终端编号
        //$mygoods['biz_content']['terminal_id'] = $terminal_id;
        // 公共回传参数，如果请求时传递了该参数，则返回给商户时会在异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝
        $arr_data['biz_content']['passback_params'] = urlencode($module);

        //$mygoods['biz_content']['passback_params'] = urlencode(json_encode(['code' => $code, 'data' => $passback_params]));
        //服务器端异步响应地址
        $arr_data['biz_content'] = json_encode($arr_data['biz_content'], JSON_UNESCAPED_UNICODE);

        $str_pay_info = $this->getSignContent($arr_data);

        //签名
        $str_sign = $this->alipaySign($module,$str_pay_info);

        $arr_data['sign'] = $str_sign;

        return $this->_curlPost('https://openapi.alipay.com/gateway.do', $arr_data); //json
    }

    /*************************支付宝支付签名需要使用到的方法*******************************/

    /**
     * RSA验签
     * @param string $data 待签名数据
     * @param string $sign 需要验签的签名
     * @return bool
     */
    public function verify($arr_data, $sign,$module)
    {
        $pubKey = config('alipay.'.$module)['public_key'];// 支付宝公钥
        $res = "-----BEGIN PUBLIC KEY-----\n" .
            wordwrap($pubKey, 64, "\n", true) .
            "\n-----END PUBLIC KEY-----";
        ($res) or die;//die('支付宝RSA公钥错误。请检查公钥文件格式是否正确');

        //调用openssl内置方法验签，返回bool值
        $result = (bool)openssl_verify($arr_data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256);

        return $result;
    }

    /**
     * 支付宝签名
     * @param string $data 需要签名的字符串
     * @param string $signType
     * @return string
     */
    private function alipaySign($module,$data, $signType = "RSA2")
    {
        $priKey = config('alipay.'.$module)['private_key'];
        $res = openssl_get_privatekey($priKey);

        ($res) or die('您使用的私钥格式错误，请检查RSA私钥配置');

        if ("RSA2" == $signType) {
            openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256);
        } else {
            openssl_sign($data, $sign, $res);
        }
        openssl_free_key($res);
        $sign = base64_encode($sign);
        return $sign;
    }

    /**
     * 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
     * @param array $params 需要拼接的数组
     * @return string 拼接完成以后的字符串
     */
    public function getSignContent($params, $type = 0)
    {
        ksort($params);

        $stringToBeSigned = "";
        $i = 0;
        foreach ($params as $k => $v) {
            if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {
                // 转换成目标字符集
                $v = $this->characet($v, 'UTF-8');
                if ($type == 1) {
                    $v = urlencode($v);
                }
                if ($i == 0) {
                    $stringToBeSigned .= "$k" . "=" . "$v";
                } else {
                    $stringToBeSigned .= "&" . "$k" . "=" . "$v";
                }
                $i++;
            }
        }

        unset ($k, $v);
        return $stringToBeSigned;
    }

    /**
     * 转换字符集编码
     * @param $data
     * @param $targetCharset
     * @return string
     */
    private function characet($data, $targetCharset)
    {

        if (!empty($data)) {
            $fileType = 'UTF-8';
            if (strcasecmp($fileType, $targetCharset) != 0) {
                $data = mb_convert_encoding($data, $targetCharset, $fileType);
            }
        }
        return $data;
    }

    /**
     * 校验$value是否非空
     *  if not set ,return true;
     *    if is null , return true;
     **/
    private function checkEmpty($value)
    {
        if (!isset($value))
            return true;
        if ($value === null)
            return true;
        if (trim($value) === "")
            return true;

        return false;
    }
}
